"""
====================================================================
Copyright (C) 2012-2013 Eric Blond

This file is part of Neptune.

Neptune is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Neptune is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Neptune.  If not, see <http://www.gnu.org/licenses/>.
====================================================================

Created on Nov 17, 2012

@author: Eric
"""

import sys
import threading
import Queue
from . import page
from . import exception
from . import introspect
from . import thread
from . import utils

#######

def _hook():
    thread.GLOBALS.reactor = None

thread.register(_hook)

#######

def reactor():
    return thread.GLOBALS.reactor

def lift(obj, sync = True):
    reactor_ = reactor()
    if reactor_:
        return reactor_.lift(obj, sync)
    else:
        return obj

#######

def _reactor_main(reactor, kwargs):
    utils.LOGGER.info('Started reactor for handler [%s].', reactor._cls.__name__)
    thread.init_thread()
    thread.GLOBALS.reactor = reactor
    handler = reactor._cls(**kwargs)
    reactor._result[0] = handler
    reactor._event.set()
    while not reactor._stop:
        message = reactor._queue.get()
        if message and not reactor._stop:
            (is_sync, msg_fun, msg_args, msg_kwargs) = message
            if is_sync:
                try:
                    reactor._result[0] = (False, msg_fun(*msg_args, **msg_kwargs))
                except Exception:
                    reactor._result[0] = (True, sys.exc_info())
                reactor._event.set()
            else:
                try:
                    msg_fun(*msg_args, **msg_kwargs)
                except Exception:
                    utils.LOGGER.warning('Message raised an exception!', exc_info = True)
    utils.LOGGER.info('Reactor for handler [%s] ended.', reactor._cls.__name__)

#######

class Reactor(object):
    
    def __init__(self, cls):
        self._cls = cls
        self._cls_cells = introspect.PageInfo(Class = cls).CellMapping.keys()
        self._queue = Queue.Queue()
        self._lock = threading.RLock()
        self._event = threading.Event()
        self._result = [None]
        self._thread = None
        self._stop = False
        self._handler = None

    def start(self, **kwargs):
        if self._thread is not None:
            raise exception.InvalidOperationError('Reactor has already started')
        self._thread = threading.Thread(target = _reactor_main,
                                        args = (self, kwargs))
        self._thread.start()
        self._event.wait()
        self._event.clear()
        self._handler = self._result[0]

    def handler(self, sync = True):
        return self.lift(self._handler, sync)

    def call(self, function, *args, **kwargs):
        if thread.GLOBALS.thread_id == self._handler._NEPTUNE_thread_id:
            return function(*args, **kwargs)
        else:
            with self._lock:
                self._queue.put((True, function, args, kwargs))
                self._event.wait()
                self._event.clear()
                (is_exc, result) = self._result[0]
                if is_exc:
                    (typ, value, traceback) = result
                    raise typ, value, traceback
                else:
                    return self.lift(result)

    def send(self, function, *args, **kwargs):
        with self._lock:
            self._queue.put((False, function, args, kwargs))

    def stop(self):
        if self._thread is None:
            raise exception.InvalidOperationError('Reactor has not started')
        self._stop = True
        self._queue.put(None)
        self._thread.join()
        self._thread = None
        self._stop = False
        self._handler = None

    def __enter__(self):
        self._lock.acquire()
    
    def __exit__(self, typ, value, traceback):
        self._lock.release()
        if typ is not None:
            raise typ, value, traceback

    def lift(self, obj, sync = True):
        if isinstance(obj, SyncPageProxy) and not sync:
            return AsyncPageProxy(obj._NEPTUNE_reactor, obj._NEPTUNE_instance)
        elif isinstance(obj, AsyncPageProxy) and sync:
            return SyncPageProxy(obj._NEPTUNE_reactor, obj._NEPTUNE_instance)
        elif isinstance(obj, SyncFunctionProxy) and not sync:
            return AsyncFunctionProxy(obj._reactor, obj._function)
        elif isinstance(obj, AsyncFunctionProxy) and sync:
            return SyncFunctionProxy(obj._reactor, obj._function)
        elif isinstance(obj, page.Page):
            if sync:
                return SyncPageProxy(self, obj)
            else:
                return AsyncPageProxy(self, obj)
        elif callable(obj):
            if sync:
                return SyncFunctionProxy(self, obj)
            else:
                return AsyncFunctionProxy(self, obj)
        else:
            return obj

#######

class Sync(object):
    pass

class Async(object):
    pass

class PageProxy(object):

    def __init__(self, reactor, instance):
        object.__setattr__(self, '_NEPTUNE_reactor', reactor)
        object.__setattr__(self, '_NEPTUNE_instance', instance)

class FunctionProxy(object):
    
    def __init__(self, reactor, function):
        self._reactor = reactor
        self._function = function

#######

class SyncPageProxy(PageProxy, Sync):
    
    def __getattr__(self, name):
        if name in self._NEPTUNE_reactor._cls_cells:
            return self._NEPTUNE_reactor.call(getattr, self._NEPTUNE_instance, name)
        else:
            return SyncFunctionProxy(self._NEPTUNE_reactor, getattr(self._NEPTUNE_instance, name))

    def __setattr__(self, name, value):
        return self._NEPTUNE_reactor.call(setattr, self._NEPTUNE_instance, name, value)

    def __delattr__(self, name):
        self._NEPTUNE_reactor.call(delattr, self._NEPTUNE_instance, name)

#######

class SyncFunctionProxy(FunctionProxy, Sync):
    
    def __call__(self, *args, **kwargs):
        return self._reactor.call(self._function, *args, **kwargs)

#######

class AsyncPageProxy(PageProxy, Async):
    
    def __getattr__(self, name):
        if name in self._NEPTUNE_reactor._cls_cells:
            raise ValueError("Asynchronous call doesn't support get() operations")
        return AsyncFunctionProxy(self._NEPTUNE_reactor, getattr(self._NEPTUNE_instance, name))

    def __setattr__(self, name, value):
        return self._NEPTUNE_reactor.send(setattr, self._NEPTUNE_instance, name, value)

    def __delattr__(self, name):
        self._NEPTUNE_reactor.send(delattr, self._NEPTUNE_instance, name)

#######

class AsyncFunctionProxy(FunctionProxy, Async):
    
    def __call__(self, *args, **kwargs):
        return self._reactor.send(self._function, *args, **kwargs)

#######

class Barrier(object):

    def __init__(self):
        self._event = threading.Event()
        self._result = None
        
    def wait(self):
        self._event.wait()
        self._event.clear()
        return self._result
    
    def send(self, value = None):
        self._result = value
        self._event.set()
